package net.pantolomin.nayo;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketOption;
import java.nio.channels.SocketChannel;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import net.pantolomin.nayo.api.ConnectionEventsListener;

/**
 * A client using Java NIO as the backing implementation
 */
@SuppressWarnings("rawtypes")
public class NioClient extends NioConnector<SocketChannel> {
	/** The task in charge of verifying if a connection timeout occurred */
	private ConnectionTimeout cnxTimeout;

	/**
	 * Create the client
	 * 
	 * @param provider
	 *          the provider
	 * @param hostAddress
	 *          the remote host's address
	 * @param port
	 *          the port to connect to
	 * @param socketOptions
	 *          the socket options
	 * @param eventsListener
	 *          the listener for incoming data
	 * @throws IOException
	 *           if the communication channel could not be opened
	 */
	NioClient(NioProvider provider, InetAddress hostAddress, int port, Map<SocketOption, Object> socketOptions, ConnectionEventsListener eventsListener)
			throws IOException {
		super(provider, hostAddress, port, socketOptions, eventsListener);
	}

	@Override
	protected SocketChannel initChannel() throws IOException {
		// Create a non-blocking socket channel
		SocketChannel socketChannel = SocketChannel.open();
		socketChannel.configureBlocking(false);
		return socketChannel;
	}

	@Override
	protected <T> void setOption(SocketChannel channel, SocketOption<T> option, T value) throws IOException {
		channel.setOption(option, value);
	}

	@Override
	@SuppressWarnings("synthetic-access")
	void start() {
		// Start the connection
		this.cnxTimeout = new ConnectionTimeout();
		Timer timer = null;
		try {
			timer = getProvider().getTimer();
			timer.schedule(this.cnxTimeout, getProvider().getConfiguration().getConnectionTimeout());
		} catch (NullPointerException e) {
			if (timer == null) {
				throw new IllegalStateException("Provider is stopped");
			}
			throw e;
		} catch (IllegalStateException e) {
			throw new IllegalStateException("Provider is stopped");
		}
		getProvider().getSelectionLoop().makeConnection(getChannel(), //
				new InetSocketAddress(getHostAddress(), getPort()));
	}

	@Override
	void close() throws IOException {
		// In case close was called before either a connection or a timeout
		finishConnect();
		super.close();
	}

	/**
	 * Notifies that the connection is finished
	 */
	void finishConnect() {
		if (this.cnxTimeout != null) {
			this.cnxTimeout.cancel();
		}
	}

	/**
	 * The detector for a timeout when connecting
	 */
	private final class ConnectionTimeout extends TimerTask {
		@Override
		public void run() {
			getProvider().getSelectionLoop().declareTimeout(getChannel());
		}
	}
}